﻿using System;
using System.DirectoryServices.Protocols;
using System.Runtime.InteropServices;
using ActiveDirectoryUtilities.CommonLibrary.Unsafe;

namespace ActiveDirectoryUtilities.DirectoryServiceAccess
{
	public class GlobalMethods
	{
		private GlobalMethods() { }

		/// <summary>
		/// Convert a 64-bit/large integer into a DateTime object.  This is for scenarios where this is the number of 100ns ticks since January 1st, 1601.
		/// </summary>
		/// <param name="attribute">The DirectoryAttribute object that contains the large integer data.</param>
		/// <returns>A DateTime object.  For null values January 1, 1601 12:00 AM is returned.</returns>
		public static DateTime ConvertInt64ToDateTime(DirectoryAttribute attribute)
		{
			if ((attribute != null) && (attribute.Count > 0))
				if (attribute[0] != null)
					if (Convert.ToInt64(attribute[0]) != 0)
						return new DateTime(1601, 1, 1).AddTicks(Convert.ToInt64(attribute[0]));
			return Convert.ToDateTime(null);
		}

		/// <summary>
		/// Convert a string(Generalized-Time) to a DateTime Object.
		/// </summary>
		/// <param name="attribute">The DirectoryAttribute object that contains the large integer data.</param>
		/// <returns>A DateTime object.  For null values January 1, 1601 12:00 AM is returned.</returns>
		public static DateTime ConvertGeneralizedTimeToDateTime(DirectoryAttribute attribute)
		{
			if ((attribute != null) && (attribute.Count > 0))
				if (attribute[0] != null)
				{
					int year = Convert.ToInt32(attribute[0].ToString().Substring(0, 4));
					int	month = Convert.ToInt32(attribute[0].ToString().Substring(4, 2));
					int day = Convert.ToInt32(attribute[0].ToString().Substring(6, 2));
					int hour =  Convert.ToInt32(attribute[0].ToString().Substring(8, 2));
					int minute = Convert.ToInt32(attribute[0].ToString().Substring(10 ,2));
					int second = Convert.ToInt32(attribute[0].ToString().Substring(12, 2));
					DateTimeKind kind = DateTimeKind.Utc;	//assume everything is in UTC
					return new DateTime(year, month, day, hour, minute, second, kind);
				}
			return Convert.ToDateTime(null);
		}

		/// <summary>
		/// Helper method to convert a byte array to a hex string.
		/// </summary>
		/// <param name="bytes">Byte Array retrieved from AD that represents a Unicode string.</param>
		/// <returns>The string conversion of the Unicode values represented by the bytes in the array.</returns>
		//public static string ConvertByteArrayToString(byte[] bytes)
		//{
		//    char[] chars = new char[bytes.Length];
		//    for (int i = 0; i < bytes.Length; i++)
		//    {
		//        chars[i] = (char)bytes[i];
		//    }
		//    return new string(chars);
		//}

		///// <summary>
		/// Helper method to convert a DirectoryAttribute to a GUID.
		/// </summary>
		/// <param name="attribute">DirectoryAttribute object that it is desired to convert.</param>
		/// <returns>GUID of the requested object.</returns>
		public static Guid ConvertDirectoryAttributeByteArrayToGuid(DirectoryAttribute attribute)
		{
			if ((attribute != null) && (attribute.Count > 0))
			{
				if (attribute[0] != null)
				{
					return new Guid((byte[])attribute[0]);
				}
			}
			return Guid.Empty;
		}

		/// <summary>
		/// Convert a Guid to a LDAP searchable string format.  http://support.microsoft.com/kb/899663/en-us
		/// </summary>
		/// <param name="guid">GUID to convert into string</param>
		/// <returns>LDAP searchable string format</returns>
		public static string ConvertGuidToLdapSearchString(Guid guid)
		{
			string resultingGuidSearch = string.Empty;
			//Break the Guid up according to instructions in http://support.microsoft.com/kb/899663/en-us
			//1. Use a backslash (\) to separate each pair of numbers in the GUID. 

			//After you complete this step, the GUID is in the following format:
			//\a3\ac\ae\e7-\cb\14-\44\66-\83\ce-\1d\d1\17\b5\46\dc 
			//2. Reverse the pairs in the first section, the second section, and the third section. Do not change the fourth section or the fifth section. 

			//After you complete this step, the GUID is in the following format: 
			//\e7\ae\ac\a3-\14\cb-\66\44-\83\ce-\1d\d1\17\b5\46\dc 
			//3. Remove the hyphens. 

			//After you complete this step, the GUID is in the following format: 
			//\e7\ae\ac\a3\14\cb\66\44\83\ce\1d\d1\17\b5\46\dc
			//Use this GUID in the LDAP query. 

			//split the string into parts and deal with one part at a time
			string[] guidParts = guid.ToString("B").Split('-');

			//reverse the first three parts of it, one at a time
			for (int i = 0; i < 3; i++)
			{
				for (int j = 2; j <= guidParts[i].Length; j = j + 2)
				{
					resultingGuidSearch = resultingGuidSearch + "\\" + guidParts[i].Substring(guidParts[i].Length - j, 2);
				}
			}

			//insert "\" on the last two sections
			for (int i = 3; i < guidParts.Length; i++)
			{
				for (int j = 0; j <= guidParts[i].Length - 2; j = j + 2)
				{
					resultingGuidSearch = resultingGuidSearch + "\\" + guidParts[i].Substring(j, 2);
				}
			}
			return resultingGuidSearch;
		}

		internal static NtDsApi.DS_NAME_RESULT_ITEM[] DsCrackNames(string[] stringsToCrack
			, NtDsApi.DS_NAME_FLAGS flags
			, NtDsApi.DS_NAME_FORMAT formatOffered
			, NtDsApi.DS_NAME_FORMAT formatDesired)
		{
			NtDsApi.DsHandle hDS = new NtDsApi.DsHandle();
			try
			{
				uint dsBindResult = NtDsApi.DsBind(null, null, out hDS);
				if ((dsBindResult == 0) && (!hDS.IsInvalid))
				{
					NtDsApi.DS_NAME_RESULT hCracked = null;
					try
					{
						uint dsCrackNamesResult = NtDsApi.DsCrackNames(
							hDS
							, flags
							, formatOffered
							, formatDesired
							, (uint)stringsToCrack.Length
							, stringsToCrack
							, out hCracked);

						if (dsCrackNamesResult == 0)
						{
							//NtDsApi.DS_NAME_RESULT results = new NtDsApi.DS_NAME_RESULT();
							//results.cItems = (uint)Marshal.ReadInt32(hCracked.DangerousGetHandle());
							//results.rItems = Marshal.ReadIntPtr(hCracked, Marshal.OffsetOf(typeof(NtDsApi.DS_NAME_RESULT), "rItems").ToInt32());
							NtDsApi.DS_NAME_RESULT_ITEM[] safeResults = new NtDsApi.DS_NAME_RESULT_ITEM[hCracked.cItems];
							hCracked.rItems.CopyTo(safeResults, 0);
							//IntPtr currentResult = results.rItems;
							//for (uint i = 0; i < results.cItems; i++)
							//{
							//    safeResults[i] = (NtDsApi.DS_NAME_RESULT_ITEM)Marshal.PtrToStructure(currentResult, typeof(NtDsApi.DS_NAME_RESULT_ITEM));
							//    currentResult = (IntPtr)((int)currentResult + Marshal.SizeOf(safeResults[i]));
							//}
							return safeResults;
						}
						else
						{
							throw new DirectoryServiceAccessException("DsCrackNames returned error:  " + dsCrackNamesResult.ToString());
						}
					}
					finally
					{
						if (hCracked != null)
						{
							NtDsApi.DsFreeNameResult(hCracked);
						}
					}
				}
				else
				{
					throw new DirectoryServiceAccessException("DsCrackNames failed to bind with error:  " + dsBindResult.ToString());
				}
			}
			finally
			{
				if (hDS != null)
				{
					hDS.Close();
				}
			}
		}
	}
}
